<?php

/**
 * @desc : Class handle session of Web Server and store it to Memcached
 * to use this, just init this class by following statement
 * $oSession = new CSession2DB();
 *
 * $Id: Session.php 1437 2012-03-07 10:44:10Z locth $
 * $Author: locth $
 *
 *
 *
 */
class SGN_Session
{
    /**
     * Name of session
     *
     * @var string
     */
    private $sessionName = '';
    /**
     * Check value make sure that session come from right browser
     *
     * @var string
     */
    private $check = '';
    /**
     * current system time
     *
     * @var integer
     */
    private $now = 0;
    /**
     * number of minute that sesion time out
     *
     * @var integer
     */
    private $expiry = 0;
    /**
     * Check sessionID existance
     *
     * @var boolean
     */
    private $isExistSessionID = false;
    /**
     *
     * Cache runtime & error log
     *
     *
     * @var string
     */
    private $_logFile = '/home/sgn/logs/cache.error.log';
    /**
     *
     * @var object Zend_cache or implementation of Zend_Cache
     */
    private $_cache = null;

    /**
     * @desc : Constructor of class, init session, create key of session, and Expiry date
     *
     * @return none
     */
    public function __construct($cacheObject)
    {
        if ($_GET['disableSessionCookie'] == 1) {
            ini_set('session.use_cookies', "0");
        }
        session_start();
        return;
        $this->_cache = &$cacheObject;
        $this->gc();
        $this->check = md5($_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR']);
        $this->now = time();
        $this->start = microtime(true);
        session_set_save_handler(
                        array(&$this, 'open'), // open
                        array(&$this, 'close'), // close
                        array(&$this, 'read'), // read
                        array(&$this, 'write'), // write
                        array(&$this, 'destroy'), // destroy
                        array(&$this, 'gc')
                        ); // gc
        ini_set('session.cache_limiter', 'nocache, must-revalidate-revalidate');
        ini_set('session.trans_sid', true);
        session_start();
    }
    /**
     * 
     * Reset logfile path
     * 
     * 
     * @param string $path
     * 
     * @return void
     * 
     */
    public function setLogFile($logFilePath)
    {
        $this->_logFile = $logFilePath;
    }
    /**
     * @desc : Open one session and clean session expiried
     *
     * @param String $sSavePath
     * @param String $sSessionName
     * @return boolean
     */
    public function open($savePath, $sessionName)
    {
        $this->gc();
        $this->sessionName = $sessionName;
        return true;
    }

    /**
     * @desc : Close one session
     *
     * @return boolean
     */
    public function close()
    {
        return true;
    }

    /**
     * @desc : Get one session in Database by SessionID
     *
     * @param String $sSessionID
     * @return String
     */
    public function read($sessionID)
    {
        $this->_sessionID = $sessionID;
        try {
            $this->_value = $this->_cache->load($sessionID);
            if ($this->_value == '') {
                error_log("LOAD EMPTY OCCURS\n", 3, $this->_logFile);
            }
        } catch (Exception $e) {
            error_log(__LINE__ . "_\$sessionID: " . $e . "\n", 3, $this->_logFile);
        }
        error_log("Load with1 \$sessionID:   " . $sessionID . " data: " . $this->_value . "\n", 3, $this->_logFile);
        $this->isExistSessionID = false;
        if ($this->_value) {
            $this->isExistSessionID = true;
            return $this->_value;
        }
        return false;
    }

    /**
     * @desc : Write one session to Database
     *
     * @param String $sessionID
     * @param String $data
     * @return boolean
     */
    public function write($sessionID, $data)
    {
        $ts = microtime(true);
        // use internal data for fast requests (up to 0.5 sec.)
        if ($sessionID == $this->_sessionID && (! $this->_value || $ts - $this->start < 0.5)) {
            $oldData = $this->_value;
        } else { // else read data again
            $oldData = $this->read($sessionID);
        }
        if ($oldData === false) {
            $oldData = '';
        }
        $data = $oldData !== false ? $this->_fixvars($data, $oldData) : $data;
        if ($data !== $oldData) {
            try {
                error_log("Save with \$sessionID:   " . $sessionID . ", data $data\n", 3, $this->_logFile);
                $saveResult = $this->_cache->save($data, $sessionID);
                if ($saveResult === false) {
                    error_log("SAVE FAILED\n", 3, $this->_logFile);
                }
            } catch (Exception $e) {
                error_log(__LINE__ . "_\$sessionID: " . $e . "\n", 3, $this->_logFile);
            }
            return $saveResult;
        }
        return true;
    }

    /**
     * @desc : Destroy one session
     *
     * @param String $sSessionID
     * @return boolean
     */
    public function destroy($sessionID)
    {
        try {
            $this->_cache->remove($sessionID);
        } catch (Exception $e) {
            error_log("\$sessionID: " . $e . "\n", 3, $this->_logFile);
        }
    }

    /**
     * @desc: Clean session is expiried
     *
     * @param integer $maxlifetime
     * @return boolean
     */
    public function gc($maxlifetime = null)
    {
        // not sure if this is necessary if memcache lifetime is applied
    }

    /**
     * 
     * Get Session value
     * 
     * @param string $key
     * 
     * @return string
     * 
     */
    public static function get($key)
    {
        return $_SESSION[$key];
    }

    /**
     * 
     * Set Session value
     * 
     * 
     * @param string $key
     * @param string $value
     * 
     * @return void
     */
    public static function set($key, $value)
    {
        $_SESSION[$key] = $value;
    }

    /**
     * Merge vars with old vars and apply unsets
     * 
     * @param string $vars
     * @param string $oldvars
     * 
     * @return string
     * 
     */
    private function _fixvars($vars, $oldvars)
    {
        $ts = microtime(true);
        if ($oldvars !== false) {
            $a_oldvars = $this->unserialize($oldvars);
            if (is_array($a_oldvars)) {
                //                foreach ((array) $this->unsets as $k)
                //                    unset($a_oldvars[$k]);
                $newvars = $this->serialize(array_merge((array) $a_oldvars, (array) $this->unserialize($vars)));
            } else
                $newvars = $vars;
        }
        $this->unsets = array();
        return $newvars;
    }

    /**
     * Serialize session data
     * 
     * @param string $vars
     * @return string
     * 
     */
    private function serialize($vars)
    {
        $data = '';
        if (is_array($vars))
            foreach ($vars as $var => $value)
                $data .= $var . '|' . serialize($value);
        else
            $data = 'b:0;';
        return $data;
    }

    /**
     * Unserialize session data
     * http://www.php.net/manual/en/function.session-decode.php#56106
     * 
     * @param string $str
     * 
     * @return string
     * 
     */
    private function unserialize($str)
    {
        $str = (string) $str;
        $endptr = strlen($str);
        $p = 0;
        $serialized = '';
        $items = 0;
        $level = 0;
        while ($p < $endptr) {
            $q = $p;
            while ($str[$q] != '|')
                if (++ $q >= $endptr)
                    break 2;
            if ($str[$p] == '!') {
                $p ++;
                $has_value = false;
            } else {
                $has_value = true;
            }
            $name = substr($str, $p, $q - $p);
            $q ++;
            $serialized .= 's:' . strlen($name) . ':"' . $name . '";';
            if ($has_value) {
                for (;;) {
                    $p = $q;
                    switch (strtolower($str[$q])) {
                        case 'n': /* null */
                        case 'b': /* boolean */
                        case 'i': /* integer */
                        case 'd': /* decimal */
                            do
                                $q ++; while (($q < $endptr) && ($str[$q] != ';'));
                            $q ++;
                            $serialized .= substr($str, $p, $q - $p);
                            if ($level == 0)
                                break 2;
                            break;
                        case 'r': /* reference  */
                            $q += 2;
                            for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q ++)
                                $id .= $str[$q];
                            $q ++;
                            $serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */
                            if ($level == 0)
                                break 2;
                            break;
                        case 's': /* string */
                            $q += 2;
                            for ($length = ''; ($q < $endptr) && ($str[$q] != ':'); $q ++)
                                $length .= $str[$q];
                            $q += 2;
                            $q += (int) $length + 2;
                            $serialized .= substr($str, $p, $q - $p);
                            if ($level == 0)
                                break 2;
                            break;
                        case 'a': /* array */
                        case 'o': /* object */
                            do
                                $q ++; while (($q < $endptr) && ($str[$q] != '{'));
                            $q ++;
                            $level ++;
                            $serialized .= substr($str, $p, $q - $p);
                            break;
                        case '}': /* end of array|object */
                            $q ++;
                            $serialized .= substr($str, $p, $q - $p);
                            if (-- $level == 0)
                                break 2;
                            break;
                        default:
                            return false;
                    }
                }
            } else {
                $serialized .= 'N;';
                $q += 2;
            }
            $items ++;
            $p = $q;
        }
        return unserialize('a:' . $items . ':{' . $serialized . '}');
    }
}
